/**************************************************************
 * client_mplayer.c - 
 * Copyright (c) 2009  Qin Xiaoxue  <crs.chin@gmail.com>
 *
 * This file is  free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
**************************************************************/

#include <sys/select.h>
#include <sys/types.h>
#include <sys/time.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <wait.h>
#include <pthread.h>
#include <stdarg.h>

#include "client.h"

#define PLUGIN_NAME "MPlayer"

enum __mplayer_status{
  STATUS_PLAYING,
  STATUS_PAUSED,
  STATUS_IDLE,
  STATUS_EXITED
};


struct mplayer_control{
  FILE *in;
  FILE *out;
  gboolean running;
  enum __mplayer_status   status;
  gchar **exec_argv;
  gchar **exec_env;
  gchar *exec;
  const gchar *err;
  pid_t pid;
  pthread_t status_id;
  pthread_t playback_id;
};


static char *default_argv[] =  {"-slave", "-quiet", "-msglevel", "all=2", "-idle", NULL};



#define RETURN_IF_INSANE_BROKEN_PIPE(control, retval)	\
  do{													\
    if(! control)										\
      return (retval);									\
    if( ! control->running )  {							\
      control->err = "mplayer not running";				\
      return (retval);									\
    }													\
  }while(0)

#define RETURN_IF_IDLE_INSANE_BROKEN_PIPE(control, retval)	\
  do{														\
    if(! control)											\
      return (retval);										\
    if( ! control->running )  {								\
      control->err = "mplayer not running";					\
      return (retval);										\
    }														\
    if(control->status == STATUS_IDLE)  {					\
      control->err = "idle";								\
      return (retval);										\
    }														\
  }while(0)


#define __reliableXX(func)											\
  static gboolean __reliable_##func(FILE *fp, gchar *buf, gint len)	\
  {																	\
    gint ok_##func;													\
    gint i = 0, conti = 0, try = 3;									\
																	\
    while(len)  {													\
      ok_##func = func(buf + i, 1, len, fp);						\
      if(! ok_##func)  {											\
		if(! conti)  {												\
		  conti = 1;												\
		  continue;													\
		}															\
		try--;														\
		if(! try)													\
		  return FALSE;												\
      }																\
      len -= ok_##func;												\
      i += ok_##func;												\
      conti = 0;													\
      try = 3;														\
    }																\
																	\
    retrun TRUE;													\
  }

__reliableXX(fwrite);		/* __reliable_fwrite() */
__reliableXX(fread);		/* __reliable_fread() */


/*
 *NOTE: spaces in cmd and its args may cause ocnfustions to mplayer,
 *quotes are needed when there is spaces, call it with args like this:
 *__send_cmd(control, "loadfile", "\"blash blash.mp3\"", NULL);
 */
static void __send_cmd(FILE *in, const gchar *cmd, ...)
{
  va_list ap;
  gchar *arg;

  __reliable_fwrite(in, (gchar *)cmd, strlen(cmd));

  va_start(ap, cmd);
  while(arg = va_arg(ap, gchar *))  {
    fputc(' ', in);
    __reliable_fwrite(in, arg, strlen(arg));
  }
  va_end(ap);

  fput('\n', in);
  fflush(in);
}

static void  __eat_out(FILE *fp)
{
  gchar tmp[100];
  fd_set readset;
  struct timeval tv;
  gint ret;
  gint fd = fileno(fp);

  tv.tv_sec = tv.tv_usec = 0;
  FD_ZERO(&readset);
  FD_SET(fd, &readset);

  for(;;)  {
    ret = select(fd + 1, &readset, NULL, NULL, &tv);
    if(! ret)
      return;
    if(ret == -1)
      continue;
    if(FD_ISSET(fd, &readset))
      fread(tmp, 1, 100, fp);
  }
}


static gboolean __cmd_result(FILE *out)
{
  fd_set readset;
  struct timeval tv;

  FD_ZERO(&readset);
  FD_SET(fileno(out), &readset);
  tv.tv_sec = 0;
  tv.tv_usec = 100000000;

  return select(fileno(out) + 1, &readset, NULL, NULL, &tv) > 0;
}


static gboolean mplayer_play(void *data)
{
  struct mplayer_control *control = (struct mplayer_control *)data;

  RETRUN_IF_IDLE_INSANE_BROKEN_PIPE(control, FALSE);

  if(control->status == STATUS_PLAYING)
    return TRUE;

  __send_cmd(control->in, "pause", NULL);
  control->status = STATUS_PLAYING;

  return TRUE;
}


static gboolean mplayer_seek(void *data, gint msec);
static gboolean mplayer_pause(void *data);

static gboolean mplayer_stop(void *data)
{
  struct mplayer_control *control = (struct mplayer_control *)data;

  RETRUN_IF_IDLE_INSANE_BROKEN_PIPE(control, FALSE);

  if(! mplayer_seek(data, 0))
    return FALSE;

  return mplayer_pause(data);
}


static gboolean mplayer_pause(void *data)
{
  struct mplayer_control *control = (struct mplayer_control *)data;

  RETRUN_IF_IDLE_INSANE_BROKEN_PIPE(control, FALSE);

  if(control->status == STATUS_PAUSED)
    return TRUE;

  __send_cmd(control->in, "pause", NULL);
  control->status = STATUS_PAUSED;
  return TRUE;
}


static gboolean mplayer_toggle(void *data)
{
  struct mplayer_control *control = (struct mplayer_control *)data;

  RETRUN_IF_IDLE_INSANE_BROKEN_PIPE(control, FALSE);

  if(control->status == STATUS_PLAYING)
    return mplayer_pause(data);

  if(control->status == STATUS_PAUSED)
    return mplayer_play(data);
}


static gboolean mplayer_quit(void *data)
{
  struct mplayer_control *control = (struct mplayer_control *)data;

  if(! control )
    return FALSE;
  if(! control->running)
    return TRUE;

  __cend_cmd(control->in, "quit");
  pthread_join(control->status_id, NULL);
  return TRUE;
}



static gboolean mplayer_seek(void *data, gint msec)
{
  gfloat sec;
  gchar *arg;
  struct mplayer_control *control = (struct mplayer_control *)data;

  RETRUN_IF_IDLE_INSANE_BROKEN_PIPE(control, FALSE);

  sec = msec/1000.0;
  arg = g_strdup_printf("%f", sec);
  __send_cmd(control->in, "seek", arg, "2", NULL);
  control->status = STATUS_PLAYING;

  return TRUE;
}


static gboolean mplayer_seek_rel(void *data, gint msec)
{
  gfloat sec;
  gchar *arg;
  struct mplayer_control *control = (struct mplayer_control *)data;

  RETRUN_IF_IDLE_INSANE_BROKEN_PIPE(control, FALSE);

  sec = msec/1000.0;
  arg = g_strdup_printf("%f", sec);
  __send_cmd(control->in, "seek", arg, "0", NULL);
  control->status = STATUS_PLAYING;

  return TRUE;
}



  /* volume range is supposed to be between 0 and 100 */
static gint     mplayer_volume_get(void *data, gint channel)
{
  gint  vol = -1;
  struct mplayer_control *control = (struct mplayer_control *)data;

  RETRUN_IF_IDLE_INSANE_BROKEN_PIPE(control, -1);

  __eat_out(control->out);
  __send_cmd(control->in, "get_property", "volume", NULL);
  control->status = STATUS_PLAYING;
  fscanf(control->out, "ANS_volume=%d", &vol);

  return vol;
}


static gboolean mplayer_volume_set(void *data, gint channel, gint vol)
{
  gchar buf[100];
  struct mplayer_control *control = (struct mplayer_control *)data;

  RETRUN_IF_IDLE_INSANE_BROKEN_PIPE(control, FALSE);

  g_sprintf(buf, "%d", vol);
  __send_cmd(control->in, "volume", buf, "1", NULL);
  control->status = STATUS_PLAYING;

  return TRUE;
}


/*
 *FIXME: mplayer don't load and save playlist automatically
 *
 *gboolean mplayer_next(void *data)
 *{}
 *gboolean mplayer_prev(void *data)
 *{}
 */


static gint mplayer_status(void *data)  /* player status */
{
  struct mplayer_control *control = (struct mplayer_control *)data;

  RETRUN_IF_INSANE_BROKEN_PIPE(control, -1);

  switch(control->status)  {
  case STATUS_PLAYING:
    return PLAYER_STATUS_PLAY;
  case STATUS_PAUSED:
    return PLAYER_STATUS_PAUSE;
  case STATUS_IDLE:
    return PLAYER_STATUS_STOP;
  default:
    control->err = "BUG: unknow player status!";
    return -1;
  }
}


static gint mplayer_time(void *data)    /* current playinpositionin millisecond */
{
  gfloat time;
  struct mplayer_control *control = (struct mplayer_control *)data;

  RETRUN_IF_IDLE_INSANE_BROKEN_PIPE(control, -1);

  __eat_out(control->out);
  __send_cmd(control->in, "get_time_pos", NULL);
  control->status = STATUS_PLAYING;
  if(fscanf(control->out, "ANS_TIME_POSITION=%f", &time))
    return (gint)(time * 1000);

  return -1;
}



static gchar *mplayer_get_music_file(void*data)
{
  gchar *fname = NULL, *a, *b;
  gint size;
  struct mplayer_control *control = (struct mplayer_control *)data;

  RETRUN_IF_IDLE_INSANE_BROKEN_PIPE(control, NULL);

  __eat_out(control->out);
  __send_cmd(control->in, "get_file_name", NULL);
  control->status = STATUS_PLAYING;
  if(__cmd_result(control->out))
    if(getline(&fname, &size, control->out) != -1)
      if(fname)  {
		a = strchr(fname, '=');
		a += 2;
		b = fname + strlen(fname);
		b--;
		memmove(fname, a, b - a);
		*(fname + (b - a)) = '\0';
		return fname;
      }

  control->err = "unknown err";
  return NULL;
}


static gboolean __find_str_at_end(FILE *fp, const gchar *str)
{
  gchar *res;
  gint len, ok = 0;

  while(__cmd_result(fp))  {
    res = NULL;
    if(getline(&res, &len, fp)  != -1)  {
      if(strstr(res, "Starting playback..."))  {
		g_free(res);
		return TRUE;
      }
      g_free(res);
    }
  }

  return FALSE;
}


static void *__spawn_playback_watchdog(struct mplayer_control *control)
{
}


static gboolean mplayer_play_music_file(void *data, const gchar *filename)
{
  gchar *safe_name;
  struct mplayer_control *control = (struct mplayer_control *)data;

  if(! filename)
    return FALSE;
  if(! control->running)
    if(! __spawn_mplayer(control))
      return FALSE;

  safe_name = g_strdup_printf("\"%s\"", filename);
  __send_cmd(control->in, "loadfile", safe_name, NULL);
  g_free(safe_name);  
  if(__find_str_at_end(control->out, "Starting, playback..."))  {
    __spawn_playback_watchdog(control);
    control->status = STATUS_PLAYING;
    return TRUE;
  }

  return FALSE;
}



static gboolean mplayer_is_video(void *data)
{
  gchar *ans = NULL, *p;
  gint len;
  struct mplayer_control *control = (struct mplayer_control *)data;

  RETRUN_IF_IDLE_INSANE_BROKEN_PIPE(control, FALSE);

  __eat_out(control->out);
  __send_cmd(control->in, "get_video_codec", NULL);
  if(getline(&ans, &len, control->out) != -1) 
	if(ans)  {
	  p = strchr(ans, '=');
	  if(p)  {
		p++;
		if(*p == *(p + 1))  {
		  g_free(ans);
		  return FALSE;
		}
	  }
	  g_free(ans);
	  return TRUE;
	}

  return FALSE;
}


/* FIXME: mplayer doesn't show subtitle even the subtitle file is
 * loaded successfully */
static gboolean mplayer_set_subtitle(void *data, const gchar *srt) /* for players playing video */
{
  gchar *safe_srt;
  struct mplayer_control *control = (struct mplayer_control *)data;

  RETRUN_IF_IDLE_INSANE_BROKEN_PIPE(control, FALSE);
  if(! srt)
    return FALSE;

  safe_srt = g_strdup_printf("\"%s\"", srt);
  __eat_out(control->out);
  __send_cmd(control->in, "sub_load", safe_srt, NULL);
  control->status = STATUS_PLAYING;
  g_free(safe_srt);

  return TRUE;
}


static gchar *__parse_ans(FILE *out)
{
  gint len;
  gchar *ans = NULL, *a, *b;

  if(__cmd_result(out)) 
    if(getline(&ans, &len, out) != -1)
      if(ans)  {
	a = strchr(ans, '=');
	if(a)  {
	  b = strrchr(ans, '\'');
	  if(a != b)  {
	    a++;
	    if(a != b)  {
	      memmove(ans, a, b - a);
	      *(ans + (b - a) + 1) = '\0';
	      return ans;
	    }
	  }
	}
	g_free(ans);
      }

  return NULL;
}


static gchar *__get_meta(void *data, const gchar *cmd)
{
  struct mplayer_control *control = (struct mplayer_control *)data;

  RETRUN_IF_IDLE_INSANE_BROKEN_PIPE(control, NULL);

  __eat_out(control->out);
  __send_cmd(control->in, cmd, NULL);
  return __parse_ans(control->out);
}

  /* music file meta info */
static gchar *mplayer_getartist(void *data)
{
  return __get_meta(data, "get_meta_artist");
}


static gchar *mplayer_get_title(void *data)
{
  return __get_meta(data, "get_meta_title");
}


static gchar *mplayer_get_album(void *data)
{
  return __get_meta(data, "get_meta_album");
}


static gchar *mplayer_get_year(void *data)
{
  return __get_meta(data, "get_meta_year");
}


static gchar *mplayer_get_genre(void *data)
{
  return __get_meta(data, "get_meta_genre");
}


static gchar *mplayer_get_track(void *data)
{
  return __get_meta(data, "get_meta_track");
}


static gchar *mplayer_get_comment(void *data)
{
  return __get_meta(data, "get_meta_comment");
}


/* FIXME: ot supported by mplayer */
/* static gboolean mplayer_set_artist(void *data, const gchar *artist) */
/* { */

/* } */


/* static gboolean mplayer_set_title(void *data, const gchar *title) */
/* { */
/* } */


/* static gboolean mplayer_set_album(void *data, const gchar *album) */
/* { */
/* } */


/* static gboolean mplayer_set_year(void *data, const gchar *year) */
/* { */
/* } */


/* static gboolean mplayer_set_genre(void *data, const gchar *genre) */
/* { */
/* } */


/* static gboolean mplayer_set_track(void *data, const gint *track) */
/* { */
/* } */


/* static gboolean mplayer_set_comment(void *data, const gchar *comment) */
/* { */
/* } */


/*FIXME:not implemented. */

/* static gboolean mplayer_set_file_changed(void *data, void (*callback)(void *udata), void *udata) */
/* { */
/* } */


/* static gboolean mplayer_set_player_started(void *data, void (*callback)(void *udata), void *udata) */
/* { */
/* } */


/* static gboolean mplayer_set_player_stoped(void *data, void (*callback)(void *udata), void *udata) */
/* { */
/* } */


/* static gboolean mplayer_set_player_exited(void *data, void (*callback)(void *udata), void *udata) */
/* { */
/* } */



/* static gboolean mplayer_get_option(void *data, gint opt, void *result) */
/* { */
/* } */


/* static gboolean mplayer_set_option(void *data, gint opt, void *optarg) */
/* { */

/* } */



static gboolean mplayer_is_error(void *data)
{
  struct mplayer_control *control = (struct mplayer_control *)data;

  if(! control)
    return FALSE;

  return control->err != NULL || ! control->running;
}


static void mplayer_clear_error(void *data)
{
  struct mplayer_control *control = (struct mplayer_control *)data;

  if(! control)
    return;

  control->err = NULL;
}


static void mplayer_perror(void *data)
{
  struct mplayer_control *control = (struct mplayer_control *)data;

  if(! control)
    return ;

  if(! control->running)
    fprintf(stderr, "** :%s plugin: %s\n", 
	    PLUGIN_NAME, "mplayer exited unexpectedly");
  else if(control->err)
    fprintf(stderr, "** :%s plugin: %s\n", 
	    PLUGIN_NAME, control->err);
  else
    fprintf(stderr, "-- :%s plugin: %s\n", 
	    PLUGIN_NAME, "no error occurred");

}


static void __sigpipe_handler(int num)
{
}


static void __sigalrm_handler(int num)
{
}


static struct client *__init_client_struct(struct mplayer_control *control)
{
  struct client *client = g_new0(struct client, 1);

  client->c_servername = PLUGIN_NAME;
  client->c_data = control;

  client->play = mplayer_play;
  client->stop = mplayer_stop;
  client->pause = mplayer_pause;
  client->toggle = mplayer_toggle;
  client->quit = mplayer_quit;
  client->seek = mplayer_seek;
  client->seek_rel = mplayer_seek_rel;
  client->volume_get = mplayer_volume_get;
  client->volume_set = mplayer_volume_set;

  client->status = mplayer_status;
  client->time = mplayer_time;

  client->get_music_file = mplayer_get_music_file;
  client->play_music_file = mplayer_play_music_file;

  //  client->get_artist = mplayer_get_artist;
  client->get_title = mplayer_get_title;
  client->get_album = mplayer_get_album;
  client->get_year = mplayer_get_year;
  client->get_genre = mplayer_get_genre;
  client->get_track = mplayer_get_track;
  client->get_comment = mplayer_get_comment;

  client->is_error = mplayer_is_error;
  client->clear_error = mplayer_clear_error;
  client->perror = mplayer_perror;

  return client;
}


static void *__mplayer_watchdog(void *data)
{
  struct mplayer_control *control = (struct mplayer_control *)data;
  int status;

  for(;;)
    if(waitpid(control->pid, &status, 0) == control->pid)
      if(WIFEXITED(status))  {
	control->running = FALSE;
	fclose(control->in);
	control->in = NULL;
	fclose(control->out);
	control->out = NULL;
	control->status = STATUS_EXITED;
	control->pid = 0;
	return (void *)0;
      }
}


static void     __start_mplayer_watchdog(struct mplayer_control *control)
{
  if(! pthread_create(& control->status_id, NULL, __mplayer_watchdog, control))
    fprintf(stderr, "** unable to moniter mplayer's status.\n");
}


gboolean __spawn_mplayer(struct mplayer_control *control)
{
  int in[2], out[2];
  pid_t id;
  int status;

  if(pipe(in) || pipe(out))  {
    g_free(control);
    perror("** pipe");
    return FALSE;
  }

  control->in = fdopen(in[0], "w");
  control->out = fdopen(out[1], "r");

  signal(SIGALRM, __sigalrm_handler);
  id = fork();
  if(id == -1)  {
    perror("** fork mplayer");
    close(in[0]);
    close(in[1]);
    close(out[0]);
    close(out[1]);
    return FALSE;
  }

  if(id)  {
    close(in[1]);
    close(out[0]);
    control->pid = id;
    sleep(10);
    waitpid(id, &status, WNOHANG);
    if(WIFEXITED(status))  {
      g_free(control);
      fclose(control->in);
      fclose(control->out);
      return FALSE;
    }
    control->running = TRUE;
    control->status = STATUS_IDLE;
    __start_mplayer_watchdog(control);
    return TRUE;
  }else  {
    fclose(stdin);
    fclose(stdout);
    fclose(stderr);
    if(dup2(in[1], 0) == -1  ||
       dup2(out[0], 1) == -1 ||
       dup2(out[0], 2) == -1)
      _exit(-1);
    kill(getppid(), SIGALRM);
    execvp(control->exec, control->exec_argv);
    _exit(-1);
  }
}


static void __free_control_struct(struct mplayer_control *control)
{
  if(! control)
    return;

  if(control->exec)
    g_free(control->exec);
  if(control->exec_env)
    g_strfreev(control->exec_env);
  fclose(control->in);
  fclose(control->out);
  g_free(control);
}


static struct client *client_mplayer_init(const gchar *client_name,
										  const gchar *exec,
										  gint argc,
										  gchar **argv)
{
  struct mplayer_control *control;

  control = g_new0(struct mplayer_control, 1);

  if(exec)
    control->exec = g_strdup(exec);
  else
    control->exec = g_strdup("mplayer");

  control->exec_argv = default_argv;
  control->exec_env = NULL;

  if(__spawn_mplayer(control))
    return __init_client_struct(control);

  __free_control_struct(control);

  return NULL;
}


static void client_mplayer_exit(struct client *client)
{
  struct mplayer_control *control;

  if(! client)
    return;
  control = (struct mplayer_control *)client->c_data;
  if(g_strcmp0(PLUGIN_NAME, client->c_servername))
    return;

  mplayer_quit(client->c_data);
  __free_control_struct(control);

  g_free(client);
}



struct client_plugin_info PLUGIN_EXPORT_SYMBOL =  {
  .init = client_mplayer_init,
  .exit = client_mplayer_exit,

  .player_name = PLUGIN_NAME,
  .author = "X Q",
  .version = "0.1",
  .mail = "crs.chin@gmail.com",
  .description = "plugin to interact with mplayer"
};


/******************* testing code ****************************/

#ifdef TEST_CLIENT_MPLAYER

int main(int argc, char **argv)
{

}

#endif
